﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
//异常，与继承和多态类似，是在日常练习中很少使用，但在大型项目中相当重要的语法，
//关于异常的基本了解：
//1.常见的异常可分为两种，一种是库中的方法的某个行为不符合预期抛出的（如new）；
//	而另一种就是用户使用try_throw抛出的
//  注：try与catch必须搭配使用，且每个try块至少需要搭配一个catch语句
//2.这两种异常的处理方式都相同，需要在抛出异常的调用链中添加合适的catch语句接收异常，
//	若整个调用链都没有合适的catch语句，则程序会在运行时报错
//  注：用户搭建的调用链需保证调用的子函数位于上一层函数的try语句中
//3.若抛出的异常被正确接收，则程序会从对应的catch语句后继续运行，不会崩溃
//	注：被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。根据抛出对象的
//	类型和内容，程序的抛出异常部分告知异常处理部分到底发生了什么错误。
//4.一但有方法抛出异常，则会现在抛出异常的try下查找合适的catch，若找到，则进行处理，
//  若未找到，则进行“栈展开”，直观感受是不会继续执行抛出异常后的语句，
//	直至从调用链中找到正确的catch语句，会继续执行catch后的语句，而底层行为为，
//  先析构抛出异常的方法中初始化的对象，之后依次析构调用链中对象，直到正确的catch语句
//5.抛出异常对象后，会生成一个异常对象的拷贝，因为抛出的异常对象可能是一个局部对象，所以会
//	生成一个拷贝对象，这个拷贝的对象会在catch子句后销毁。（这里的处理类似于函数的传值返
//	回）
//6.为满足一些特殊需要，可使用throw语句将接收到的异常再次抛出
//7.由于抛出异常后未正确接收，则会导致程序崩溃的机制，所以一般会在main函数末尾添加try_catch(...)语句（能够接收所有异常的语句），
//  以确保出现意料之外的异常时，不会造成程序崩溃
//8.由于栈展开的特殊机制，很有可能会导致抛出异常前new，但由于抛出异常，却无法正确delete，造成内存泄漏问题，
//	由此为契机，诞生了智能指针与RAII机制（资源获取即初始化）……
//9.throw第一次抛出异常时会进行一次拷贝构造，且为保证进行栈展开，拷贝出的对象会被存放在一个特殊的区域，不会因为函数栈的销毁而销毁，
//	当使用throw重新抛出异常时，就不会调用拷贝构造了，而是继续跑出第一次抛出的异常
//	当使用catch接收异常时，若使用引用类型接收，则不会进行拷贝构造，若使用值接收，则会再次调用拷贝构造，且此拷贝出的对象位于catch语句所在函数的栈中，
//	但需要注意，catch(...)不会实际接收任何异常，因此，catch(...)不会调用任何拷贝构造
//10.设计异常时应分为3种级别，分别是基本保证，强保证，不抛保证，
//  其含义依次为，保证不内存泄漏（RAII为基石）；保证操作不是完全成功就是完全失败（如Copy_Swap惯用法），
//  若抛出异常，也能保证程序状态完全回滚到之前的状态；保证绝对不会抛出异常（关键字noexcept）；
//	这些是设计时的约定与契约，需尽力遵守
//注：不抛保证并非指函数内部一定不会抛异常，而是就算抛异常，也不会泄露到该函数外，且一定能在该函数内部进行正确处理
//11.对于类中的析构函数与移动构造，需保证其绝对不会抛出异常（特别是析构函数），
//	在检查过后，需为其添加强保证（noexcept）
//注：如果析构函数内部必须进行可能失败的操作（必须使用可能抛异常的函数），
//    那么必须在该操作内部捕获并处理掉所有异常，绝不允许异常传播到析构函数之外​​


//catch接收异常时的异常匹配规则：
//1.一般情况下抛出对象和catch是类型完全匹配的，如果有多个类型匹配的，就选择离他位置更近的
//	那个
//	注：可以使用对应类型的引用进行接收，以减少拷贝
//2.但是也有一些例外：允许从非常量向常量的类型转换，也就是权限缩小；允许数组转换成指向数组
//	元素类型的指针，函数被转换成指向函数的指针；允许从派生类向基类类型的转换，这个点非常实
//	用，实际中继承体系基本都是用这个方式设计的。
//3.如果到main函数，异常仍旧没有被匹配就会终止程序，不是发生严重错误的情况下，我们是不期望
//	程序终止的，所以一般main函数中最后都会使用catch(...)，它可以捕获任意类型的异常，但是是
//	不知道异常错误是什么

//try_catch的简单示例
void test1()
{
	int a = 0;
	cin >> a;
	try
	{
		if (a > 10)//当a>10时，抛出一个string异常
		{
			string s("a过大！");
			throw s;
		}
	}
	catch (const string& errid)//接收所有类型为string类型的异常，并进行相应处理
	{
		cout << errid << endl;
	}
}


//栈展开示例
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
void test2s(int n)
{
	A a;
	try {
		if (n == 1)
		{
			throw string("注意！n == 1");
		}
	}
	catch (int errid)//int类型与上述抛出的异常类型string不匹配
	{
		cout << errid << endl;
	}
	cout << "test2scatch语句之后" << endl;//由于上述try抛出异常，因此不会继续执行该语句
}
void test2()
{
	try {
		test2s(1);
	}
	catch (const string& errid)
	{
		cout << errid << endl;
	}
	cout << "void test2()catch之后" << endl;
}
//上述结果：
//A()
//~A()
//注意！n == 1
//void test2()catch之后
//-》可以发现，test2s中创建的a对象成功被析构，但test2scatch语句后的cout语句未被执行


//重新抛出异常示例：
void test3()
{
	try {
		throw A();
	}
	catch (const A& errid)
	{
		cout << "catch (const A& errid)" << endl;
		//throw errid;-》错误，不会抛出接收到的源异常，而可能抛出errid的拷贝，且拷贝也会新建在特殊区域
		throw;//正确的重新抛出
	}
}


//在大型项目中，常利用catch可接收派生类的性质与多态进行搭配，
//以捕捉异常，示例：
class B
{
public:
	virtual void err() const = 0;//纯虚函数
};
class Bf : public B
{
public:
	void err() const override
	{
		cout << "发生了一类异常！" << endl;
	}
};
class Bs : public B
{
public:
	void err() const override
	{
		cout << "发生了二类异常！" << endl;
	}
};
void test4()
{
	try
	{
		throw Bf();
	}
	catch (const B& errid)//切片接收了上述throw的Bf对象
	{
		//利用多态，调用重写函数打印错误信息
		errid.err();//发生了一类异常！
	}
}

//标准库异常，推荐使用const T&类型进行接收，并且需要注意，try_catch配套的，
//就算是标准库中会抛出异常的函数（如new），也只是省略了throw，仍需将该函数写到try中，
//才能使用对应的调用链中的catch进行接收
void test5()
{
	try
	{
		int* p = new int[1000000000000000000];
	}
	catch (const std::bad_alloc& errid)//new抛出的异常是std::bad_alloc，应使用const std::bad_alloc&进行接收
	{
		cout << "new分配内存失败！！！" << endl;
	}
}
int main()
{
	//大型项目中的try_catch(...)正确方式，直接将main中所有内容写到try中（包括return 0），
	//try后紧跟各种catch，最终以catch(...)结尾
	try
	{
		//test1();
		//test2();
		//test3();
		//test4();
		test5();
		return 0;
	}
	catch (...)
	{
		cout << "出现了未知异常！" << endl;
	}
}